package org.geektimes.web.mvc;

import org.apache.commons.lang.StringUtils;
import org.geektimes.projects.dependency.context.ComponentContext;
import org.geektimes.web.mvc.controller.Controller;
import org.geektimes.web.mvc.controller.PageController;
import org.geektimes.web.mvc.controller.RestController;
import org.geektimes.web.mvc.util.AntPathMatcher;

import javax.annotation.Resource;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.logging.Logger;
import java.util.stream.Stream;

import static java.util.Arrays.asList;
import static org.apache.commons.lang.StringUtils.substringAfter;

public class FrontControllerServlet extends HttpServlet {

    private Logger logger = Logger.getLogger(FrontControllerServlet.class.getName());
    /**
     * 请求路径和 Controller 的映射关系缓存
     */
    private Map<String, Controller> controllersMapping = new HashMap<>();

    /**
     * 请求路径和 {@link HandlerMethodInfo} 映射关系缓存
     */
    private Map<String, HandlerMethodInfo> handleMethodInfoMapping = new HashMap<>();

    private ComponentContext context = ComponentContext.getInstance();
    private static final String REQUEST_MATCHER_PATH = "request-matcher-path";
    private static final String REQUEST_MATCHER_CONTROLLER = "request-matcher-controller";
    private static final String REQUEST_MATCHER_PATH_VARIABLES = "request-matcher-path-variables";

    /**
     * 初始化 Servlet
     *
     * @param servletConfig
     */
    @Override
    public void init(ServletConfig servletConfig) {
        initHandleMethods();
    }

    /**
     * 读取所有的 RestController 的注解元信息 @Path
     * 利用 ServiceLoader 技术（Java SPI）
     */
    private void initHandleMethods() {
        ServiceLoader<Controller> controllers = ServiceLoader.load(Controller.class);
        logger.severe("成功加载 Controller 的数量是否大于0 : " + controllers.iterator().hasNext());
        for (Controller controller : controllers) {
            Class<?> controllerClass = controller.getClass();
            Path pathFromClass = controllerClass.getAnnotation(Path.class);
            String classGlobalPath = pathFromClass.value();
            Method[] publicMethods = controllerClass.getDeclaredMethods();
            String requestPath = "";
            // 处理方法支持的 HTTP 方法集合
            for (Method method : publicMethods) {
                if (!method.isSynthetic()) {
                    Set<String> supportedHttpMethods = findSupportedHttpMethods(method);
                    Path pathFromMethod = method.getAnnotation(Path.class);
                    if (pathFromMethod != null) {
                        requestPath = classGlobalPath + pathFromMethod.value();
                        requestPath = StringUtils.replace(requestPath, "//", "/");
                        handleMethodInfoMapping.put(requestPath,
                                new HandlerMethodInfo(requestPath, method, supportedHttpMethods));
                        controllersMapping.put(requestPath, controller);
                    }
                }
            }
        }
    }

    /**
     * 获取处理方法中标注的 HTTP方法集合
     *
     * @param method 处理方法
     * @return
     */
    private Set<String> findSupportedHttpMethods(Method method) {
        Set<String> supportedHttpMethods = new LinkedHashSet<>();
        for (Annotation annotationFromMethod : method.getAnnotations()) {
            HttpMethod httpMethod = annotationFromMethod.annotationType().getAnnotation(HttpMethod.class);
            if (httpMethod != null) {
                supportedHttpMethods.add(httpMethod.value());
            }
        }

        if (supportedHttpMethods.isEmpty()) {
            supportedHttpMethods.addAll(asList(HttpMethod.GET, HttpMethod.POST,
                    HttpMethod.PUT, HttpMethod.DELETE, HttpMethod.HEAD, HttpMethod.OPTIONS));
        }

        return supportedHttpMethods;
    }

    /**
     * SCWCD
     *
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    @Override
    public void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 建立映射关系
        // requestURI = /a/hello/world
        String requestURI = request.getRequestURI();
        // contextPath  = /a or "/" or ""
        String servletContextPath = request.getContextPath();
        String prefixPath = servletContextPath;
        // 映射路径（子路径）
        String requestMappingPath = substringAfter(requestURI,
                StringUtils.replace(prefixPath, "//", "/"));


        // 映射到 Controller, 包含解析请求pathVariable
        Map handlerPathVariableMap = getController(requestMappingPath);
        Controller controller = (Controller) handlerPathVariableMap.get(REQUEST_MATCHER_CONTROLLER);

        if (controller != null) {

            HandlerMethodInfo handlerMethodInfo =
                    getHandlerMethodInfo((String) handlerPathVariableMap.get(REQUEST_MATCHER_PATH));

            try {
                if (handlerMethodInfo != null) {

                    String httpMethod = request.getMethod();

                    if (!handlerMethodInfo.getSupportedHttpMethods().contains(httpMethod)) {
                        // HTTP 方法不支持
                        response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
                        return;
                    }

                    String viewPath = null;
                    // 默认提供的 Controller 标记接口
                    if (controller instanceof Controller) {

                        // 为 Controller 注入 Service
                        injectResource(controller);

                        Method executeMethod = handlerMethodInfo.getHandlerMethod();
                        Controller sourceController =
                                controllersMapping.getOrDefault(handlerMethodInfo.getRequestPath()
                                        , null);
                        executeMethod.setAccessible(true);
                        // 暂时约定如果传参,则仅传入 request 和 response
                        if (executeMethod.getParameterCount() == 2 &&
                                executeMethod.getParameterTypes()[0].isAssignableFrom(HttpServletRequest.class) &&
                                executeMethod.getParameterTypes()[1].isAssignableFrom(HttpServletResponse.class)) {

                            if (Objects.nonNull(sourceController)) {
                                viewPath = (String) executeMethod.invoke(sourceController, request, response);
                            }
                        } else {
                            if (executeMethod.getParameterCount() > 0) {
                                Object[] args = new Object[executeMethod.getParameterCount()];
                                Map variablesMap = (Map) handlerPathVariableMap.get(REQUEST_MATCHER_PATH_VARIABLES);

                                for (int i = 0; i < executeMethod.getParameters().length; i++) {
                                    Parameter parameter = executeMethod.getParameters()[i];
                                    PathParam annotation = parameter.getAnnotation(PathParam.class);
                                    if(null != annotation && StringUtils.equals(annotation.value(), parameter.getName())) {
                                        Class<?> type = parameter.getType();
                                        if(type.isAssignableFrom(Long.class)){
                                            args[i] = Long.valueOf((String) variablesMap.get(parameter.getName()));
                                        }
                                    } else if(parameter.getType().isAssignableFrom(HttpServletRequest.class)){
                                        args[i] = request;
                                    }else if(parameter.getType().isAssignableFrom(HttpServletResponse.class)) {
                                        args[i] = response;
                                    }

                                }
                                viewPath = (String) executeMethod.invoke(sourceController, args);
                            } else {
                                viewPath = (String) executeMethod.invoke(sourceController);
                            }
                        }
                    } else if (controller instanceof PageController) {
                        PageController pageController = PageController.class.cast(controller);
                        viewPath = pageController.execute(request, response);
                        // 页面请求 forward
                        // request -> RequestDispatcher forward
                        // RequestDispatcher requestDispatcher = request.getRequestDispatcher(viewPath);
                        // ServletContext -> RequestDispatcher forward
                        // ServletContext -> RequestDispatcher 必须以 "/" 开头

                    } else if (controller instanceof RestController) {
                        // TODO
                    }

                    if (Objects.nonNull(viewPath) && !viewPath.startsWith("/")) {
                        viewPath = "/" + viewPath;
                    }
                    if (Objects.nonNull(viewPath)) {
                        ServletContext servletContext = request.getServletContext();
                        RequestDispatcher requestDispatcher = servletContext.getRequestDispatcher(viewPath);
                        requestDispatcher.forward(request, response);
                    }
                    return;
                }
            } catch (Throwable throwable) {
                if (throwable.getCause() instanceof IOException) {
                    throw (IOException) throwable.getCause();
                } else {
                    throw new ServletException(throwable.getCause());
                }
            }
        }
    }

    /**
     * 根据请求获取最匹配的处理方法,并将路径参数填充至入参, 方法入参需要使用@PathParam指定路径参数的名称
     *
     * @param requestMappingPath
     * @return
     */
    private HandlerMethodInfo getHandlerMethodInfo(String requestMappingPath) {
        HandlerMethodInfo handlerMethodInfo = handleMethodInfoMapping.get(requestMappingPath);
        return handlerMethodInfo;
    }

    /**
     * 获取与请求路径最为匹配的Controller, 包含路径变量的情况
     *
     * @param requestMappingPath
     * @return
     */
    private Map getController(String requestMappingPath) {
        Map<String, Object> handlerPathVariableMap = new HashMap<>();
        AntPathMatcher pathMatcher = new AntPathMatcher();
        List<String> matchingPatterns = new ArrayList<>();
        for (String pattern : controllersMapping.keySet()) {
            if (pathMatcher.match(pattern, requestMappingPath)) {
                matchingPatterns.add(pattern);
            }
        }
        Comparator<String> patternComparator = pathMatcher.getPatternComparator(requestMappingPath);
        String bestMatch = null;
        if (!matchingPatterns.isEmpty()) {
            matchingPatterns.sort(patternComparator);
            if (matchingPatterns.size() > 1) {
                logger.warning("Matching patterns " + matchingPatterns);
            }
            bestMatch = matchingPatterns.get(0);
        }
        if (bestMatch != null) {
            String pathWithinMapping = pathMatcher.extractPathWithinPattern(bestMatch, requestMappingPath);

            Map<String, String> uriTemplateVariables = new LinkedHashMap<>();
            for (String matchingPattern : matchingPatterns) {
                if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
                    uriTemplateVariables = pathMatcher.extractUriTemplateVariables(matchingPattern,
                            requestMappingPath);
                }
            }
            Controller controller = controllersMapping.get(bestMatch);
            handlerPathVariableMap.put(REQUEST_MATCHER_PATH, bestMatch);
            handlerPathVariableMap.put(REQUEST_MATCHER_CONTROLLER, controller);
            handlerPathVariableMap.put(REQUEST_MATCHER_PATH_VARIABLES, uriTemplateVariables);
        }
        return handlerPathVariableMap;
    }

    private void injectResource(Controller controller) {
        Stream.of(controller.getClass().getDeclaredFields())
                .filter(field -> field.isAnnotationPresent(Resource.class))
                .forEach(field -> {
                    Resource annotation = field.getAnnotation(Resource.class);
                    String resourceName = annotation.name();
                    Object component = context.getComponent(resourceName);
                    try {
                        field.setAccessible(true);
                        if (Objects.isNull(field.get(controller))) {
                            field.set(controller, component);
                        }
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }
                });

    }

    //    private void beforeInvoke(Method handleMethod, HttpServletRequest request, HttpServletResponse response) {
    //
    //        CacheControl cacheControl = handleMethod.getAnnotation(CacheControl.class);
    //
    //        Map<String, List<String>> headers = new LinkedHashMap<>();
    //
    //        if (cacheControl != null) {
    //            CacheControlHeaderWriter writer = new CacheControlHeaderWriter();
    //            writer.write(headers, cacheControl.value());
    //        }
    //    }
}
